﻿using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;

namespace NDBGen
{
    public abstract class ExportLibrary : IDisposable
    {
        protected ExportLibrary(ComposablePartCatalog catalog)
        {
            this.Container = new CompositionContainer(catalog);
            this.Container.SatisfyImportsOnce(this);
        }

        protected CompositionContainer Container { get; private set; }

        void IDisposable.Dispose()
        {
            this.Container.Dispose();
        }
    }

    public sealed class ExportLibrary<TContract> : ExportLibrary
    {
        public ExportLibrary(ComposablePartCatalog catalog)
            : base(catalog) { }

        [ImportMany]
        public TContract[] Exports { get; private set; }
    }

    public sealed class ExportLibrary<TContract, TMetadata> : ExportLibrary
    {
        public ExportLibrary(ComposablePartCatalog catalog)
            : base(catalog) { }

        [ImportMany]
        public Lazy<TContract, TMetadata>[] Exports { get; private set; }

        public TContract[] FindAll(Func<TMetadata, Boolean> filterPredicate)
        {
            return this.Exports.Where(x => filterPredicate.Invoke(x.Metadata)).Select(x => x.Value).ToArray();
        }

        public Lazy<TContract, TMetadata>[] FindAllWithMetadata(Func<TMetadata, Boolean> filterPredicate)
        {
            return this.Exports.Where(x => filterPredicate.Invoke(x.Metadata)).ToArray();
        }

        public ReadOnlyDictionary<TKey, TContract> FindDictionary<TKey>(Func<TMetadata, Boolean> filterPredicate, Func<TMetadata, TKey> keySelector)
        {
            return new ReadOnlyDictionary<TKey, TContract>(this.Exports.Where(x => filterPredicate.Invoke(x.Metadata)).ToDictionary(x => keySelector.Invoke(x.Metadata), x => x.Value));
        }

        public ReadOnlyDictionary<Type, Type> FindTypeDictionary(Func<TMetadata, Boolean> filterPredicate, Func<TMetadata, Type> keySelector)
        {
            return new ReadOnlyDictionary<Type, Type>(this.Exports.Where(x => filterPredicate.Invoke(x.Metadata)).ToDictionary(x => keySelector.Invoke(x.Metadata), x => x.Value.GetType()));
        }

        public Lazy<TContract, TMetadata> FindOneWithMetadata(Func<Lazy<TContract, TMetadata>, Boolean> predicate)
        {
            return this.Exports.Where(predicate).FirstOrDefault();
        }

        public TContract FindOne(Func<TMetadata, Boolean> filterPredicate)
        {
            return this.Exports.Where(x => filterPredicate.Invoke(x.Metadata)).Select(x => x.Value).FirstOrDefault();
        }
    }
}